/* HackerPort driver for Linux - PIC programmer
 * version 0.7.2 date 2007-05-04
 * part of the hacker port project - http[s]://stefan.schuermans.info/hackerport/
 * Copyright (C) 2003-2004 stefan <stefan@schuermans.info>
 * Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include "hexfile.h"
#include "hkp_drv.h"



//macro for number of elements in an array
#define count( array ) (sizeof( (array) ) / sizeof( (array)[0] ))



//type for configuration
typedef struct t_pic18_cfg
{
  unsigned long chip_id_addr;
  unsigned short chip_id;
  unsigned short chip_id_mask;
  unsigned long code_addr;
  unsigned long code_size;
  unsigned long id_addr;
  unsigned long id_size;
  unsigned long cfg_addr;
  unsigned long cfg_size;
  unsigned char cfg_mask[64];
} st_pic18_cfg;



//read pic18 config file
static int read_pic18_cfg( char * filename, st_pic18_cfg * p_cfg )
{
  FILE * file;
  char line[1024], var[32], * p_chr;
  int line_no, i, j;
  unsigned long val;
  int chip_id_addr, chip_id, chip_id_mask, code_addr, code_size, id_addr, id_size, cfg_addr, cfg_size, cfg_mask;

  printf( "reading config file \"%s\"...\n", filename );

  file = fopen( filename, "rt" );
  if( file == NULL )
  {
    printf( "  could not open \"%s\": %s\n", filename, strerror( errno ) );
    return -1;
  }

  line_no = 1;
  chip_id_addr = chip_id = chip_id_mask = code_addr = code_size = id_addr = id_size = cfg_addr = cfg_size = cfg_mask = 0;
  while( ! feof( file ) )
  {
    //get next line
    fgets( line, sizeof( line ), file );
    if( strchr( line, '\n' ) == NULL )
    {
      printf( "  line %d too long\n", line_no );
      fclose( file );
      return -1;
    }

    //config mask setting
    if( strncasecmp( line, "cfg-mask ", 9 ) == 0 )
    {
      //get config mask bytes
      p_chr = line + 9;
      for( i = 0; i < count( p_cfg->cfg_mask ); i++ )
      {
        for( ; *p_chr == ' ' || *p_chr == '\t'; p_chr++ ); //skip whitespace
        if( sscanf( p_chr, "%i", &j ) != 1 )
          break;
        p_cfg->cfg_mask[i] = (unsigned char)j;
        for( ; *p_chr != ' ' && *p_chr != '\t' && *p_chr != 0; p_chr++ ); //skip non-whitespace
      }
      //too many config mask bytes
      if( sscanf( p_chr, "%i", &j ) == 1 )
      {
        printf( "  too many (more than %d) config mask bytes in line %d\n", count( p_cfg->cfg_mask ), line_no );
        fclose( file );
        return -1;
      }
      //initialize rest of config mask
      for( ; i < count( p_cfg->cfg_mask ); i++ )
        p_cfg->cfg_mask[i] = 0xFF;
      //config mask was read
      cfg_mask = 1;
    }

    //normal setting
    else if( sscanf( line, "%15[^ ] %li", var, &val ) == 2 )
    {
      if( strcasecmp( var, "chip-id-addr" ) == 0 )
      {
        p_cfg->chip_id_addr = val;
        chip_id_addr = 1;
      }
      else if( strcasecmp( var, "chip-id" ) == 0 )
      {
        p_cfg->chip_id = (unsigned short)val;
        chip_id = 1;
      }
      else if( strcasecmp( var, "chip-id-mask" ) == 0 )
      {
        p_cfg->chip_id_mask = (unsigned short)val;
        chip_id_mask = 1;
      }
      else if( strcasecmp( var, "code-addr" ) == 0 )
      {
        p_cfg->code_addr = val;
        code_addr = 1;
      }
      else if( strcasecmp( var, "code-size" ) == 0 )
      {
        p_cfg->code_size = val;
        code_size = 1;
      }
      else if( strcasecmp( var, "id-addr" ) == 0 )
      {
        p_cfg->id_addr = val;
        id_addr = 1;
      }
      else if( strcasecmp( var, "id-size" ) == 0 )
      {
        p_cfg->id_size = val;
        id_size = 1;
      }
      else if( strcasecmp( var, "cfg-addr" ) == 0 )
      {
        p_cfg->cfg_addr = val;
        cfg_addr = 1;
      }
      else if( strcasecmp( var, "cfg-size" ) == 0 )
      {
        p_cfg->cfg_size = val;
        cfg_size = 1;
      }
      else
        printf( "  unknown setting \"%s\" in line %d: ignored\n", var, line_no );
    }

    //invalid format
    else
      printf( "  unknown format of line %d: ignored\n", line_no );
 
    line_no++;   
  } //while( ! feof( file ) )

  fclose( file );

  //missing settings
  if( ! chip_id_addr )
    printf( "missing setting \"chip-id-addr\"\n" );
  if( ! chip_id )
    printf( "missing setting \"chip-id\"\n" );
  if( ! chip_id_mask )
    printf( "missing setting \"chip-id-mask\"\n" );
  if( ! code_addr )
    printf( "missing setting \"code-addr\"\n" );
  if( ! code_size )
    printf( "missing setting \"code-size\"\n" );
  if( ! id_addr )
    printf( "missing setting \"id-addr\"\n" );
  if( ! id_size )
    printf( "missing setting \"id-size\"\n" );
  if( ! cfg_addr )
    printf( "missing setting \"cfg-addr\"\n" );
  if( ! cfg_size )
    printf( "missing setting \"cfg-size\"\n" );
  if( ! cfg_mask )
    printf( "missing setting \"cfg-mask\"\n" );
  if( ! chip_id_addr || ! chip_id || ! chip_id_mask || ! code_addr || ! code_size || ! id_addr || ! id_size || ! cfg_addr || ! cfg_size || ! cfg_mask )
    return -1;

  return 0;
}


//get version of driver
static int get_info_ver( int fd_hkp_drv )
{
  struct t_hkp_drv_ver ver;
  int err;

  //show message
  printf( "driver version:\n" );

  //get driver version
  err = ioctl( fd_hkp_drv, hkp_drv_ioctl_no_ver, &ver );

  //show error
  if( err != 0 )
  {
    printf( "  error: %s\n", strerror( errno ) );
    return -1;
  }

  //show driver version
  printf( "  version: %d.%d.%d\n"
          "  date: %04d-%02d-%02d\n",
          ver.major, ver.minor, ver.revision,
          ver.year, ver.month, ver.day );
  return 0;
}

//get version of hardware
static int get_info_hw_ver( int fd_hkp_drv )
{
  struct t_hkp_drv_hw_ver hw_ver;
  int err;

  //show message
  printf( "hardware version:\n" );

  //get hardware version
  err = ioctl( fd_hkp_drv, hkp_drv_ioctl_no_hw_ver, &hw_ver );

  //show error
  if( err != 0 )
  {
    printf( "  error: %s\n", strerror( errno ) );
    return -1;
  }

  //show hardware version
  printf( "  variant code: %02X\n"
          "  version: %d.%d.%d\n"
          "  date: %04d-%02d-%02d\n",
          hw_ver.variant,
          hw_ver.major, hw_ver.minor, hw_ver.revision,
          hw_ver.year, hw_ver.month, hw_ver.day );
  return 0;
}

//get identification of hacker port
static int get_info_id( int fd_hkp_drv )
{
  struct t_hkp_drv_id id;
  int err, i;

  //show message
  printf( "identification string:\n" );

  //get identification
  err = ioctl( fd_hkp_drv, hkp_drv_ioctl_no_get_id, &id );

  //show error
  if( err != 0 )
  {
    printf( "  error: %s\n", strerror( errno ) );
    return -1;
  }

  //show identification
  printf( "  " );
  for( i = 0; i < sizeof( id.id ); i++ )
    if( (char)id.id[i] >= ' ' )
      printf( "%c", (char)id.id[i] );
    else
      printf( " " );
  printf( "\n" );
  return 0;
}

//get information about hacker port and driver
static int get_info( int fd_hkp_drv )
{
  if( get_info_ver( fd_hkp_drv ) != 0 )
    return -1;
  if( get_info_hw_ver( fd_hkp_drv ) != 0 )
    return -1;
  if( get_info_id( fd_hkp_drv ) != 0 )
    return -1;
  return 0;
}



//request ownership of PIC programming interface
static int own_pic_request( int fd_hkp_drv )
{
  struct t_hkp_drv_own own;
  int i, err;

  //show message
  printf( "requesting ownership of PIC programming interface:\n" );

  //initialize port ownership request structure
  for( i = 0; i < count( own.out ); i++ )
    own.out[i] = 0;
  for( i = 0; i < count( own.in ); i++ )
    own.in[i] = 0;
  for( i = 0; i < count( own.pwm ); i++ )
    own.pwm[i] = 0;
  for( i = 0; i < count( own.analog ); i++ )
    own.analog[i] = 0;
  own.ser = 0;
  own.i2c = 0;
  own.pic = 1; //request PIC programming interface

  //request ownership
  err = ioctl( fd_hkp_drv, hkp_drv_ioctl_no_own, &own );

  //show error
  if( err != 0 )
  {
    printf( "  error: %s\n", strerror( errno ) );
    return -1;
  }

  //check ownership
  if( own.pic > 0 )
  {
    printf( "  PIC programming interface owned\n" );
    return 0;
  }
  else
  {
    printf( "  PIC programming interface NOT owned\n" );
    return -1;
  }
}

//release ownership of PIC programming interface
static void own_pic_release( int fd_hkp_drv )
{
  struct t_hkp_drv_own own;
  int i, err;

  //show message
  printf( "releasing ownership of PIC programming interface:\n" );

  //initialize port ownership request structure
  for( i = 0; i < count( own.out ); i++ )
    own.out[i] = 0;
  for( i = 0; i < count( own.in ); i++ )
    own.in[i] = 0;
  for( i = 0; i < count( own.pwm ); i++ )
    own.pwm[i] = 0;
  for( i = 0; i < count( own.analog ); i++ )
    own.analog[i] = 0;
  own.ser = 0;
  own.i2c = 0;
  own.pic = -1; //release PIC programming interface

  //release ownership
  err = ioctl( fd_hkp_drv, hkp_drv_ioctl_no_own, &own );

  //show error
  if( err != 0 )
  {
    printf( "  error: %s\n", strerror( errno ) );
    return;
  }

  //check ownership
  if( own.pic > 0 )
    printf( "  PIC programming interface STILL owned\n" );
  else
    printf( "  PIC programming interface no more owned\n" );
}



//execute actions
static int exec_acts( int fd_hkp_drv, struct t_hkp_drv_act * p_acts, unsigned char act_cnt )
{
  int err;
  struct t_hkp_drv_acts acts;

  //execute actions
  acts.cnt = act_cnt;
  acts.p_acts = p_acts;
  err = ioctl( fd_hkp_drv, hkp_drv_ioctl_no_acts, &acts );

  //show error
  if( err != 0 )
  {
    printf( "  error: %s\n", strerror( errno ) );
    return -1;
  }

  return 0;
}



//enter programming mode
static int prog_enter( int fd_hkp_drv )
{
  struct t_hkp_drv_act act;

  usleep( 1000 );

  //set program voltage, clock and data low
  act.type = hkp_drv_act_pic_set;
  act.data.pic_set.vpp = HKP_DRV_PIC_VPP_0V;
  act.data.pic_set.pgc = HKP_DRV_PIC_PGC_LOW;
  act.data.pic_set.pgd = HKP_DRV_PIC_PGD_LOW;

  //execute action
  if( exec_acts( fd_hkp_drv, &act, 1 ) != 0 )
    return -1;

  usleep( 1000 );

  //set program voltage hight
  act.type = hkp_drv_act_pic_set;
  act.data.pic_set.vpp = HKP_DRV_PIC_VPP_13V;
  act.data.pic_set.pgc = HKP_DRV_PIC_PGC_NO_CHANGE;
  act.data.pic_set.pgd = HKP_DRV_PIC_PGD_NO_CHANGE;

  //execute action
  if( exec_acts( fd_hkp_drv, &act, 1 ) != 0 )
    return -1;

  usleep( 1000 );

  return 0;
}



//leave programming mode
static int prog_leave( int fd_hkp_drv )
{
  struct t_hkp_drv_act act;

  usleep( 1000 );

  //set program voltage, clock and data low
  act.type = hkp_drv_act_pic_set;
  act.data.pic_set.vpp = HKP_DRV_PIC_VPP_0V;
  act.data.pic_set.pgc = HKP_DRV_PIC_PGC_LOW;
  act.data.pic_set.pgd = HKP_DRV_PIC_PGD_LOW;

  //execute action
  if( exec_acts( fd_hkp_drv, &act, 1 ) != 0 )
    return -1;

  usleep( 1000 );

  return 0;
}



//PIC18 write command
static int cmd18Write( int fd_hkp_drv, unsigned char command, unsigned short data )
{
  struct t_hkp_drv_act acts[2];

  //shift out 4 bits command
  acts[0].type = hkp_drv_act_pic_out;
  acts[0].data.pic_out_in.cnt = 4;
  acts[0].data.pic_out_in.data = command;

  //shift out 16 bits data
  acts[1].type = hkp_drv_act_pic_out;
  acts[1].data.pic_out_in.cnt = 16;
  acts[1].data.pic_out_in.data = data;

  //execute actions
  if( exec_acts( fd_hkp_drv, acts, 2 ) != 0 )
    return -1;

  return 0;
}

//PIC18 write command (with timing for erasing)
static int cmd18WriteErase( int fd_hkp_drv, unsigned char command, unsigned short data )
{
  struct t_hkp_drv_act act;

  //shift out 4 bits command
  act.type = hkp_drv_act_pic_out;
  act.data.pic_out_in.cnt = 4;
  act.data.pic_out_in.data = command;

  //execute action
  if( exec_acts( fd_hkp_drv, &act, 1 ) != 0 )
    return -1;

  usleep( 5005 );

  //shift out 16 bits data
  act.type = hkp_drv_act_pic_out;
  act.data.pic_out_in.cnt = 16;
  act.data.pic_out_in.data = data;

  //execute action
  if( exec_acts( fd_hkp_drv, &act, 1 ) != 0 )
    return -1;

  return 0;
}

//PIC18 write command (with timing for programming)
static int cmd18WriteProg( int fd_hkp_drv, unsigned char command, unsigned short data )
{
  struct t_hkp_drv_act acts[3];

  //shift out 3 bits command
  acts[0].type = hkp_drv_act_pic_out;
  acts[0].data.pic_out_in.cnt = 3;
  acts[0].data.pic_out_in.data = command;

  //set clock high
  acts[1].type = hkp_drv_act_pic_set;
  acts[1].data.pic_set.vpp = HKP_DRV_PIC_VPP_NO_CHANGE;
  acts[1].data.pic_set.pgc = HKP_DRV_PIC_PGC_HIGH;
  acts[1].data.pic_set.pgd = HKP_DRV_PIC_PGD_NO_CHANGE;

  //output 4th command bit
  acts[2].type = hkp_drv_act_pic_set;
  acts[2].data.pic_set.vpp = HKP_DRV_PIC_VPP_NO_CHANGE;
  acts[2].data.pic_set.pgc = HKP_DRV_PIC_PGC_NO_CHANGE;
  acts[2].data.pic_set.pgd = command & 0x08 ? HKP_DRV_PIC_PGD_HIGH : HKP_DRV_PIC_PGD_LOW;

  //execute actions
  if( exec_acts( fd_hkp_drv, acts, 3 ) != 0 )
    return -1;

  usleep( 1001 );

  //set clock low
  acts[0].type = hkp_drv_act_pic_set;
  acts[0].data.pic_set.vpp = HKP_DRV_PIC_VPP_NO_CHANGE;
  acts[0].data.pic_set.pgc = HKP_DRV_PIC_PGC_LOW;
  acts[0].data.pic_set.pgd = HKP_DRV_PIC_PGD_NO_CHANGE;

  //shift out 16 bits data
  acts[1].type = hkp_drv_act_pic_out;
  acts[1].data.pic_out_in.cnt = 16;
  acts[1].data.pic_out_in.data = data;

  //execute actions
  if( exec_acts( fd_hkp_drv, acts, 2 ) != 0 )
    return -1;

  return 0;
}

//PIC18 read command
static int cmd18Read( int fd_hkp_drv, unsigned char command, unsigned char * p_data )
{
  struct t_hkp_drv_act acts[3];

  //shift out 4 bits command
  acts[0].type = hkp_drv_act_pic_out;
  acts[0].data.pic_out_in.cnt = 4;
  acts[0].data.pic_out_in.data = command;

  //shift out 8 zero bits
  acts[1].type = hkp_drv_act_pic_out;
  acts[1].data.pic_out_in.cnt = 8;
  acts[1].data.pic_out_in.data = 0;

  //shift in 8 bits data
  acts[2].type = hkp_drv_act_pic_in;
  acts[2].data.pic_out_in.cnt = 8;

  //execute actions
  if( exec_acts( fd_hkp_drv, acts, 3 ) != 0 )
    return -1;
  
  *p_data = (unsigned char)acts[2].data.pic_out_in.data;
  return 0;
}



//test if PIC18 is in programming mode
static int pic18Test( int fd_hkp_drv )
{
  unsigned char c;

  if( cmd18Write( fd_hkp_drv, 0x00, 0x0E55 ) != 0 || //movlw 0x55
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF5 ) != 0 || //movwf TABLAT
      cmd18Read( fd_hkp_drv, 0x02, &c ) != 0 ) //get TABLAT register
    return -1;
  if( c != 0x55 ) //check read value
  {
    printf( "error, not in programming mode: 0x%02X != 0x55\n", c );
    return -1;
  }

  if( cmd18Write( fd_hkp_drv, 0x00, 0x0EAA ) != 0 || //movlw 0xAA
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF5 ) != 0 || //movwf TABLAT
      cmd18Read( fd_hkp_drv, 0x02, &c ) != 0 ) //get TABLAT register
    return -1;
  if( c != 0xAA ) //check read value
  {
    printf( "error, not in programming mode: 0x%02X != 0xAA\n", c );
    return -1;
  }

  usleep( 1000 );

  return 0;
}

//bulk erase PIC18 memory
static int pic18BulkErase( int fd_hkp_drv )
{
  if( cmd18Write( fd_hkp_drv, 0x00, 0x0E3C ) != 0 || //bulk erase sequence
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E00 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E04 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x0C, 0x0080 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0000 ) != 0 ||
      cmd18WriteErase( fd_hkp_drv, 0x00, 0x0000 ) != 0 )
    return -1;

  usleep( 1000 );

  return 0;
}

//write bytes into PIC18 memory using multipanel mode
static int pic18WriteMultipanel( int fd_hkp_drv, unsigned long address, unsigned char * pData, unsigned long size )
{
  unsigned long panels, panel, addr, ad, addre;

  panels = size >> 13; //number of panels

  if( cmd18Write( fd_hkp_drv, 0x00, 0x8EA6 ) != 0 || //direct access to config memory
      cmd18Write( fd_hkp_drv, 0x00, 0x8CA6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x86A6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E3C ) != 0 || //multi panel write
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E00 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E06 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x0C, 0x0040 ) != 0 )
    return -1;
  for( addr = 0; addr < 0x2000; addr += 8 )
  {
    if( cmd18Write( fd_hkp_drv, 0x00, 0x8EA6 ) != 0 || //direct access to code memory
        cmd18Write( fd_hkp_drv, 0x00, 0x9CA6 ) != 0 )
      return -1;
    for( panel = 0; panel < panels; panel++ )
    {
      ad = panel << 13 | addr;
      addre = address + ad;
      if( cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((addre >> 16) & 0xFF) ) != 0 || //set address
          cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
          cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((addre >> 8) & 0xFF) ) != 0 ||
          cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
          cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)(addre & 0xFF) ) != 0 ||
          cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 ||
          cmd18Write( fd_hkp_drv, 0x0D, (unsigned short)pData[ad+0] | ((unsigned short)pData[ad+1] << 8) ) != 0 || //write data
          cmd18Write( fd_hkp_drv, 0x0D, (unsigned short)pData[ad+2] | ((unsigned short)pData[ad+3] << 8) ) != 0 ||
          cmd18Write( fd_hkp_drv, 0x0D, (unsigned short)pData[ad+4] | ((unsigned short)pData[ad+5] << 8) ) != 0 ||
          cmd18Write( fd_hkp_drv, panel + 1 < panels ? 0x0C : 0x0F, (unsigned short)pData[ad+6] | ((unsigned short)pData[ad+7] << 8) ) != 0 ) //start writing sequence if last panel
        return -1;
    }
    if( cmd18WriteProg( fd_hkp_drv, 0x00, 0x0000 ) != 0 )
      return -1;
  }

  usleep( 1000 );

  return 0;
}

//write bytes into PIC18 memory
static int pic18Write( int fd_hkp_drv, unsigned long address, unsigned char * pData, unsigned long cnt )
{
  address &= 0xFFFFFFF8; //use 8 byte aligned address
  if( cmd18Write( fd_hkp_drv, 0x00, 0x8EA6 ) != 0 || //direct access to config memory
      cmd18Write( fd_hkp_drv, 0x00, 0x8CA6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x86A6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E3C ) != 0 || //single panel write
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E00 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E06 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x0C, 0x0000 ) != 0 )
    return -1;
  for( ; cnt >= 8; address += 8, pData += 8, cnt -= 8 )
  {
    if( cmd18Write( fd_hkp_drv, 0x00, 0x8EA6 ) != 0 || //direct access to code memory
        cmd18Write( fd_hkp_drv, 0x00, 0x9CA6 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((address >> 16) & 0xFF) ) != 0 || //set address
        cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((address >> 8) & 0xFF) ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)(address & 0xFF) ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x0D, (unsigned short)pData[0] | ((unsigned short)pData[1] << 8) ) != 0 || //write data
        cmd18Write( fd_hkp_drv, 0x0D, (unsigned short)pData[2] | ((unsigned short)pData[3] << 8) ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x0D, (unsigned short)pData[4] | ((unsigned short)pData[5] << 8) ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x0F, (unsigned short)pData[6] | ((unsigned short)pData[7] << 8) ) != 0 ||
        cmd18WriteProg( fd_hkp_drv, 0x00, 0x0000 ) != 0 )
      return -1;
  }

  usleep( 1000 );

  return 0;
}

//write bytes into PIC18 config memory
static int pic18WriteConfig( int fd_hkp_drv, unsigned long address, unsigned char * pData, unsigned long cnt )
{
  address &= 0xFFFFFFFE; //use even address
  for( ; cnt >= 2; cnt -= 2, address += 2, pData += 2 )
  {
    if( cmd18Write( fd_hkp_drv, 0x00, 0x8EA6 ) != 0 || //direct access to config memory
        cmd18Write( fd_hkp_drv, 0x00, 0x8CA6 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x86A6 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0xEF00 ) != 0 || //set program counter to 0x80000
        cmd18Write( fd_hkp_drv, 0x00, 0xF800 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((address >> 16) & 0xFF) ) != 0 || //set address
        cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((address >> 8) & 0xFF) ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)(address & 0xFF) ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x0F, (unsigned short)*pData ) != 0 || //write LSB (MSB ignored)
        cmd18WriteProg( fd_hkp_drv, 0x00, 0x0000 ) != 0 ||
        cmd18Write( fd_hkp_drv, 0x00, 0x2AF6 ) != 0 || //incf TBLPTRL
        cmd18Write( fd_hkp_drv, 0x0F, (unsigned short)*(pData + 1) << 8 ) != 0 || //write MSB (LSB ignored)
        cmd18WriteProg( fd_hkp_drv, 0x00, 0x0000 ) != 0 )
     return -1;
  }

  usleep( 1000 );

  return 0;
}

//read bytes from PIC18 memory
static int pic18Read( int fd_hkp_drv, unsigned long address, unsigned char * pData, unsigned long cnt )
{
  if( cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((address >> 16) & 0xFF) ) != 0 || //set address
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF8 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)((address >> 8) & 0xFF) ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF7 ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x0E00 | (unsigned short)(address & 0xFF) ) != 0 ||
      cmd18Write( fd_hkp_drv, 0x00, 0x6EF6 ) != 0 )
    return -1;
  for( ; cnt > 0; pData++, cnt-- )
    if( cmd18Read( fd_hkp_drv, 0x09, pData ) != 0 )
      return -1;

  usleep( 1000 );

  return 0;
}



//program a PIC18 processor
static int pic18Prog( int fd_hkp_drv, st_pic18_cfg * p_pic18_cfg, unsigned char * p_code, unsigned char * p_id, unsigned char * p_cfg )
{
  unsigned long addr;
  unsigned char ChipId[2];
  unsigned short chip_id;
  unsigned char code_read[p_pic18_cfg->code_size];
  unsigned char id_read[p_pic18_cfg->id_size];
  unsigned char cfg_read[p_pic18_cfg->cfg_size];
  int success;

  //enter programming mode
  printf( "\n" );
  printf( "entering programming mode...\n" );
  prog_enter( fd_hkp_drv );
  printf( "\n" );

  //enable abuse of break command
  do
  {
    success = 0;

    //test if in programming mode
    if( pic18Test( fd_hkp_drv ) != 0 )
      break;

    //read chip id
    if( pic18Read( fd_hkp_drv, p_pic18_cfg->chip_id_addr, ChipId, 2 ) != 0 )
      break;
    chip_id = (unsigned short)ChipId[1] << 8 | (unsigned short)ChipId[0];
    printf( "chip id: 0x%04X\n", chip_id );
    if( (chip_id & p_pic18_cfg->chip_id_mask) != (p_pic18_cfg->chip_id & p_pic18_cfg->chip_id_mask) )
    {
      printf( "  exprected chip id: 0x%04X (mask 0x%04X)\n", p_pic18_cfg->chip_id, p_pic18_cfg->chip_id_mask );
      break;
    }

    //test if in programming mode
    if( pic18Test( fd_hkp_drv ) != 0 )
      break;

    //erase
    printf( "erasing...\n" );
    if( pic18BulkErase( fd_hkp_drv ) != 0 )
      break;

    //test if in programming mode
    if( pic18Test( fd_hkp_drv ) != 0 )
      break;

    //code memory
    printf( "writing code memory...\n" );
    if( pic18WriteMultipanel( fd_hkp_drv, p_pic18_cfg->code_addr, p_code, p_pic18_cfg->code_size ) != 0 )
      break;
    
    printf( "reading code memory...\n" );
    if( pic18Read( fd_hkp_drv, p_pic18_cfg->code_addr, code_read, p_pic18_cfg->code_size ) != 0 )
      break;

    printf( "comparing data of code memory...\n" );
    for( addr = 0; addr < p_pic18_cfg->code_size; addr++ )
    {
      if( p_code[addr] != code_read[addr] )
      {
        printf( "  address 0x%08lX, written: 0x%02X, read: 0x%02X\n", addr + p_pic18_cfg->code_addr, p_code[addr], code_read[addr] );
        break;
      }
    }
    if( addr < p_pic18_cfg->code_size )
      break;
    
    //test if in programming mode
    if( pic18Test( fd_hkp_drv ) != 0 )
      break;

    //id locations
    printf( "writing id locations...\n" );
    if( pic18Write( fd_hkp_drv, p_pic18_cfg->id_addr, p_id, p_pic18_cfg->id_size ) != 0 )
      break;

    printf( "reading id locations...\n" );
    if( pic18Read( fd_hkp_drv, p_pic18_cfg->id_addr, id_read, p_pic18_cfg->id_size ) != 0 )
      break;

    printf( "comparing data of id locations...\n" );
    for( addr = 0; addr < p_pic18_cfg->id_size; addr++ )
    {
      if( p_id[addr] != id_read[addr] )
      {
        printf( "  address 0x%08lX, written: 0x%02X, read: 0x%02X\n", addr + p_pic18_cfg->id_addr, p_id[addr], id_read[addr] );
        break;
      }
    }
    if( addr < p_pic18_cfg->id_size )
      break;

    //test if in programming mode
    if( pic18Test( fd_hkp_drv ) != 0 )
      break;

    //configuration bits
    printf( "writing configuration bits...\n" );
    if( pic18WriteConfig( fd_hkp_drv, p_pic18_cfg->cfg_addr, p_cfg, p_pic18_cfg->cfg_size ) != 0 )
      break;

    printf( "reading configuration bits...\n" );
    if( pic18Read( fd_hkp_drv, p_pic18_cfg->cfg_addr, cfg_read, p_pic18_cfg->cfg_size ) != 0 )
      break;

    printf( "comparing data of configuration bits...\n" );
    for( addr = 0; addr < p_pic18_cfg->cfg_size; addr++ )
    {
      if( addr < count( p_pic18_cfg->cfg_mask ) ) //within config mask
      {
        if( (p_cfg[addr] & p_pic18_cfg->cfg_mask[addr]) != (cfg_read[addr] & p_pic18_cfg->cfg_mask[addr]) )
        {
          printf( "  address 0x%08lX, written: 0x%02X, read: 0x%02X, mask: 0x%02X\n",
                  addr + p_pic18_cfg->cfg_addr,
                  p_cfg[addr], cfg_read[addr], p_pic18_cfg->cfg_mask[addr] );
          break;
        }
      }
      else //out of config mask
      {
        if( p_cfg[addr] != cfg_read[addr] )
        {
          printf( "  address 0x%08lX, written: 0x%02X, read: 0x%02X\n",
                  addr + p_pic18_cfg->cfg_addr,
                  p_cfg[addr], cfg_read[addr] );
          break;
        }
      }
    }
    if( addr < p_pic18_cfg->cfg_size )
      break;

    //test if in programming mode
    if( pic18Test( fd_hkp_drv ) != 0 )
      break;

    printf( "successfully completed\n" );
    success = 1;

  //end of abuse of break command  
  }
  while( 0 );

  //leave programming mode
  printf( "\n" );
  printf( "leaving programming mode...\n" );
  prog_leave( fd_hkp_drv );
  printf( "\n" );

  return success ? 0 : -1;
}



//main program
int main( int arg_cnt, char * * args )
{
  st_pic18_cfg pic18_cfg;
  int fd_hkp_drv;

  //print message
  printf( "HackerPort driver for Linux - PIC programmer\n"
          "version 0.7.2 date 2007-05-04\n"
          "part of the hacker port project - http[s]://stefan.schuermans.info/hackerport/\n"
          "Copyright (C) 2003-2005 stefan <stefan@schuermans.info>\n"
          "Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html\n"
          "\n" );

  //check parameters
  if( arg_cnt < 4 )
  {
    printf( "  usage: %s <pic18.cfg> <data.hex> <hacker port device>\n", args[0] );
    return -1;
  }

  //read pic18 config file
  if( read_pic18_cfg( args[1], &pic18_cfg ) != 0 )
    return -1;
  printf( "\n" );

  //allocate memory for code, id locations and config bits
  unsigned char code[pic18_cfg.code_size];
  unsigned char id[pic18_cfg.id_size];
  unsigned char cfg[pic18_cfg.cfg_size];
  memset( code, 0, pic18_cfg.code_size );
  memset( id, 0, pic18_cfg.id_size );
  memset( cfg, 0, pic18_cfg.cfg_size );

  //read data file
  st_hexfile_mem mems[] =
  {
    { .addr = pic18_cfg.code_addr, .size = pic18_cfg.code_size, code },
    { .addr = pic18_cfg.id_addr, .size = pic18_cfg.id_size, id },
    { .addr = pic18_cfg.cfg_addr, .size = pic18_cfg.cfg_size, cfg }
  };
  if( hexfile_read( args[2], mems, count( mems ) ) != 0 )
    return -1;
  printf( "\n" );

  //open hacker port
  fd_hkp_drv = open( args[3], O_RDWR );
  if( fd_hkp_drv == -1 )
  {
    printf( "  could not open hacker port device\"%s\": %s\n", args[1], strerror( errno ) );
    return -1;
  }

  //get information about hacker port and driver
  if( get_info( fd_hkp_drv ) != 0 )
  {
    close( fd_hkp_drv );
    return -1;
  }
  printf( "\n" );

  //request ownership of PIC programming interface
  if( own_pic_request( fd_hkp_drv ) != 0 )
  {
    close( fd_hkp_drv );
    return -1;
  }

  //program the PIC18
  if( pic18Prog( fd_hkp_drv, &pic18_cfg, code, id, cfg ) != 0 )
  {
    own_pic_release( fd_hkp_drv );
    close( fd_hkp_drv );
    return -1;
  }

  //release ownership of  PIC programming interface
  own_pic_release( fd_hkp_drv );

  //close hacker port
  close( fd_hkp_drv );

  return 0;
}

